﻿using System.Configuration;
using System.Globalization;
using System.Threading.Tasks;
using BlogsAPI.Interfaces;
using InstaSharp;
using InstaSharp.Models;
using InstaSharp.Models.Responses;

namespace BlogsAPI.Sites
{
    using System.Threading;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Linq;
    using System.Collections.Generic;
    using BlogsAPI.BlogsYandex;
    using RssToolkit.Rss;
    using System.Web;
    using System;
    using System.Diagnostics;
    using System.IO;
    using System.Net;
    using System.Text;
    using System.Text.RegularExpressions;
    using Abstract;

    /// <summary>
    /// Класс для взаимодействия с крупнейшей социальной сетью Instagr.am
    /// </summary>
    /// <seealso cref="http://instagr.am/developer/"/>
    [BlogServiceAPI(Description = "Instagr.am", Link = "https://instagr.am/")]
    public class Instagram : BlogServer
    {
        private static readonly Regex Validation = new Regex(@"^https?://(?:www\.)?(?:instagr\.am|instagram\.com)/", Regexoptions);
        private static readonly Regex ValidationFull = new Regex(@"^https?://(?:www\.)?(?:instagr\.am|instagram\.com)/([-_\w\.]+)(?:\s|/|$)", Regexoptions);
        
        private static readonly Regex rMediaId
            = new Regex(@"(?:\b|^)\d+_\d+(?:\b|$)", Regexoptions);

        private static Regex rLat = new Regex(@"<meta[^>]+latitude[^>]+content\W+([\d\.]+)\D", Regexoptions);
        private static Regex rLon = new Regex(@"<meta[^>]+longitude[^>]+content\W+([\d\.]+)\D", Regexoptions);


        /// <summary>
        /// Уникальный ключ для простых запросов
        /// </summary>
        public static InstagramConfig APIKey
            = new InstagramConfig("https://api.instagram.com/v1","https://api.instagram.com/oauth",
                "7e7b0bcbeae640f8a927126ce0e5422e", "", "");//common clientId for BlogsAPI

        /// <summary>
        /// Ключ приложения для запроса без аутентификации
        /// </summary>
        /// <remarks>Additional ClientId from config</remarks>
        public string ClientID
        {
            get { return ConfigurationManager.AppSettings["instagramSearchClientID"]; }
            set { ConfigurationManager.AppSettings["instagramSearchClientID"] = value; }
        }

        #region interface: BlogServer

        [DebuggerStepThrough]
        public Instagram()
        {
            Encoding = Encoding.UTF8;
        }

        /// <summary>
        /// http://www.t30p.ru/Instagram.aspx
        /// </summary>
        public override bool HasRating
        {
            get { return true; }
        }

        /// <summary>
        /// Исключение в случае нулевого секретного ключа
        /// </summary>
        private void SecretIsNotNull()
        {
            if(APIKey == null || String.IsNullOrEmpty(APIKey.ClientSecret) || String.IsNullOrEmpty(APIKey.RedirectUri))
            {
                throw new ArgumentNullException("APIKey.ClientSecret");
            }
        }

        /// <summary>
        /// проверка, является ли передаваемый урл чатью этого сервиса
        /// </summary>
        public override Boolean ValidateLink(String sLink)
        {
            return Validation.Match(sLink).Success;
        }

        /// <summary>
        /// проверка, является ли передаваемый урл чатью этого сервиса
        /// </summary>
        /// <param name="sLink">ссылка для проверки</param>
        /// <param name="userName">Имя пользователя на блогохостинге</param>
        /// <param name="bType">Тип блога - пользователь или сообщество</param>
        public override Boolean ValidateLink(String sLink, out String userName,out BlogType bType)
        {
            //фиктивный линк на профиль, который на самом деле не существует
            bType = BlogType.Unknown;
            Match m = ValidationFull.Match(sLink);
            if (m.Success)
            {
                bType = BlogType.User;
                userName = m.Groups[1].ToString();
                if (userName == "p")//была просто ссылка на фотку http://instagram.com/p/PXxOEfyHwE/
                {
                    var img = new Images.Instagram();
                    img.GetThumb(sLink);
                    if(!String.IsNullOrEmpty(img.UserName))
                    {
                        userName = img.UserName;//получили имя пользователя запостившего фотку
                    }
                }
            }
            else
            {
                userName = String.Empty;
            }
            return m.Success;
        }

        /// <summary>
        /// Получение ссылки на блог блогера
        /// </summary>
        /// <param name="userName">ник блогера</param>
        /// <param name="btype">Тип блога - пользователь или сообщество</param>
        public override String GetBlogLink(string userName, BlogType btype)
        {
            return String.Format("http://instagr.am/{0}", userName);
        }

        /// <summary>
        /// Getting user PIC
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="btype"></param>
        /// <returns></returns>
        public override string ExtractAvatar(string userName, BlogType btype)
        {
            User2 usr = GetUser(userName);
            return usr == null ? String.Empty : usr.ProfilePicture;
        }

        /// <summary>
        /// Getting followers count
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="bt"></param>
        /// <returns></returns>
        public override uint? HowManyReaders(string userName, BlogType bt)
        {
            User2 usr = GetUser(userName);
            return usr==null?null:(uint?)usr.FollowedBy.Count;
        }

        #endregion

        #region IData

        /// <summary>
        /// Извлечение номера картинки в ссылке
        /// </summary>
        /// <param name="sLink"></param>
        /// <returns></returns>
        public string ExtractMediaId(string sLink)
        {
            Match m = rMediaId.Match(sLink);
            return m.Success ? m.Groups[0].Value : "";
        }

        /// <summary>
        /// there is RSS
        /// </summary>
        public override string GetRssLink(string userName, BlogType btype)
        {
            //Have to user webstagram 
            return String.Format("http://widget.stagram.com/rss/n/{0}/",userName);
        }

        /// <summary>
        /// there is no FOAF
        /// </summary>
        public override string GetFoafLink(string userName, BlogType btype)
        {
            return "";
        }

        /// <summary>
        /// Сервис получение данных в виде RSS
        /// </summary>
        /// <param name="userName">ник блогера</param>
        /// <param name="firsttime">первое обращение для рекурсивного вызова</param>
        /// <returns></returns>
        public RssDocument GetRss(string userName, bool firsttime = true)
        {
            RssDocument oRet = null;
            try
            {
                oRet = RssDocument.Load(new Uri(GetRssLink(userName)));
            }
            catch (WebException e1)//ошибка сети или еще что
            {
                Debug.WriteLine(e1.Message);
                if (firsttime && e1.Response != null && ((HttpWebResponse)e1.Response).StatusCode == HttpStatusCode.Forbidden)
                {
                    Thread.Sleep(30000); //так как превысили лимит, то надо 30 секунд куковать
                    oRet = GetRss(userName, false);//вторая попытка
                }
            }
            catch (Exception e2)
            {
                Debug.WriteLine(e2.Message);
            }
            return oRet;
        }

        #endregion

        /// <summary>
        /// Просто получение пользователя по айди или нику
        /// </summary>
        /// <param name="userName"> ник</param>
        /// <returns>InstaSharp.Model.User or NULL</returns>
        /// <remarks>Эта функция очень часто вызывается и к сожалению ограничена, поэтому попробудем разнести на несколько аккаунтов через Authnticated request</remarks>
        public User2 GetUser(string userName)
        {
            try
            {
                using (var client = new WebClient())
                {
                    var resultData = client.DownloadString(String.Format("https://www.instagram.com/{0}/?__a=1", userName));
                    var data = Mapper.Map<User2Response>(resultData) as User2Response;
                    return data?.Data;
                }
            }catch(Exception e1)
            {
                Debug.WriteLine(e1.Message);
            }
            return null;
        }

        /// <summary>
        /// Получение списка фоток по тэгу
        /// </summary>
        /// <param name="Tag"> tag</param>
        /// <returns>InstaSharp.Model.Medias or NULL</returns>
        ///[Obsolete("Instagram requires authentication for it! So not working anymore.")]
        public Medias GetTag(string Tag)
        {
            try
            {
                using (var client = new WebClient() { Encoding = Encoding.UTF8 })
                {
                    var resultData = client.DownloadString(String.Format("https://www.instagram.com/explore/tags/{0}/?__a=1", Tag));
                    var data = Mapper.Map<Tags2Response>(resultData) as Tags2Response;
                    return data?.Tag?.Media;
                }
            }
            catch (Exception e1)
            {
                Debug.WriteLine("GetTag: "+ Tag+" returns "+e1.Message);
            }
            return null;
        }

        /// <summary>
        /// Получение данных о картинке
        /// </summary>
        /// <param name="link">ссылка</param>
        /// <param name="loadLocation">Нужен ли доп.запрос по локации</param>
        /// <returns>InstaSharp.Model.Media2 or NULL</returns>
        public Media2 GetMedia(string link, bool loadLocation = true)
        {
            Media2 media = null;
            try
            {
                if (link.Contains("/p/"))//убедились, что картинка
                {
                    using (var client = new WebClient())
                    {
                        //+www.
                        var resultData =
                            client.DownloadString(String.Format("{0}/?__a=1", link.Replace("://instagr", "://www.instagr").TrimEnd('/')));
                        var data = Mapper.Map<Media3Response>(resultData) as Media3Response;
                        media = data?.Data?.Data;

                        //Location
                        if (media?.Location != null && media.Location.has_public_page && loadLocation)
                        {
                            CookieParse(client.ResponseHeaders[HttpResponseHeader.SetCookie]);
                            client.Headers.Add(HttpRequestHeader.Cookie,sCookie);

                            var m = new Regex(@"Csrftoken\W+(\w+)\W", Regexoptions).Match(sCookie);

                            var myHttpWebRequest = Common.MakeRequest("https://www.instagram.com/explore/locations/" + media.Location.Id+"/");

                            myHttpWebRequest.Method = "POST";
                            var param = "q=ig_location(" + media.Location.Id+ ")&ref=locations::show";
                            myHttpWebRequest.Headers.Add(HttpRequestHeader.Cookie, sCookie);
                            myHttpWebRequest.Headers.Add("X-CSRFToken", m.Groups[1].Value);
                            myHttpWebRequest.ContentType = "application/x-www-form-urlencoded; charset=UTF-8";
                            myHttpWebRequest.Referer = "http://www.instagram.com";
                            // ставим False, чтобы при получении кода 302 не делать автоматический редирект
                            //myHttpWebRequest.AllowAutoRedirect = false;

                            // передаем параметры
                            string sQueryString = param;
                            //странности с кодировкой =(
                            byte[] byteArr = Encoding.GetBytes(sQueryString);
                            myHttpWebRequest.ContentLength = byteArr.Length;
                            myHttpWebRequest.GetRequestStream().Write(byteArr, 0, byteArr.Length);
                            // выполняем запрос
                            var  myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse();

                            resultData = new StreamReader(myHttpWebResponse.GetResponseStream(), Encoding).ReadToEnd();
                            myHttpWebResponse.Close();

                            m = rLat.Match(resultData);
                            decimal d;
                            if (m.Success && decimal.TryParse(m.Groups[1].Value, NumberStyles.Any, CultureInfo.InvariantCulture, out d))
                            {
                                media.Location.Latitude = d;
                            }
                            m = rLon.Match(resultData);
                            if (m.Success && decimal.TryParse(m.Groups[1].Value, NumberStyles.Any, CultureInfo.InvariantCulture, out d))
                            {
                                media.Location.Longitude = d;
                            }
                        }
                    }
                }
            }
            catch (Exception e1)
            {
                Debug.WriteLine(e1.Message);
            }
            return media;
        }

        
    }
}
